using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;

namespace TinyOsMessageForwarder
{
    public delegate void TOS1MessageArrivedDelegate(TinyOSMessage msg);
    public delegate void TOS2MessageArrivedDelegate(TinyOS2Message msg);

    public class TinyOSMessageForwarder
    {
        public event TOS1MessageArrivedDelegate TOS1MessageArrived;
        public event TOS2MessageArrivedDelegate TOS2MessageArrived;

        private string connectionString;

        public string ConnectionString
        {
            get { return connectionString; }
            set { connectionString = value; }
        }

        private Socket socket;

        private volatile bool stopped = false;
        private bool connected = false;

        public bool Connected
        {
            get { return connected; }
            set 
            {
                if (value)
                    Connect();
                else
                    Disconnect();
            }
        }

        public TinyOSMessageForwarder()
        {
            connectionString = "sf@localhost:9002";
        }

        public TinyOSMessageForwarder(string connectionString)
        {
            this.connectionString = connectionString;
            //connectionstring= "sf@host:port"
            
        }

        public void SendMessage(TinyOSMessage msg)
        {
            try
            {
                lock (this)
                {
                    byte length = (byte)(msg.Length + 5);
                    socket.Send(new byte[] { length });
                    socket.Send(msg.RawMessage, length, SocketFlags.None);
                }
            }
            catch (Exception)
            { 
            }
        }

        public void SendMessage(TinyOS2Message msg)
        {
            try
            {
                lock (this)
                {
                    byte length = (byte)(msg.Length + 8);
                    socket.Send(new byte[] { length });
                    socket.Send(new byte[] { 0 }, 1, SocketFlags.None);
                    socket.Send(msg.RawMessage, length-1, SocketFlags.None);
                }
            }
            catch (Exception)
            {
            }
        }


        private void SendString(string message)
        {
            lock (this)
            {
                byte[] messageBytes = Encoding.ASCII.GetBytes(message);
                socket.Send(messageBytes);
            }
        }
        
        private void SendMessage(byte[] rawMessageBytes)
        {
            lock (this)
            {
                byte length = Convert.ToByte(rawMessageBytes.Length);
                socket.Send(new byte[] { length });
                socket.Send(rawMessageBytes);
            }
        }

        private byte[] incomingBuffer = new byte[100];
        private Thread listenerThread;

        private void RecieveData()
        {
            try
            {
                while (!stopped)
                {

                    int nBytesRec = socket.Receive(incomingBuffer, 0, 1, SocketFlags.None);
                    if (nBytesRec > 0)
                    {
                       int len = incomingBuffer[0];
                       nBytesRec = socket.Receive(incomingBuffer, 0, len, SocketFlags.None);
                        if (nBytesRec != len)
                            throw new Exception("Not full message received!");
                        byte[] rawMessage = new byte[len];
                        Array.Copy(incomingBuffer, rawMessage, len);
                        TinyOSMessage rmsg = new TinyOSMessage(rawMessage);
                        if (TOS1MessageArrived != null)
                            TOS1MessageArrived(rmsg);
                    }
                    else
                    {
                        Console.Error.WriteLine("{0} broke the connection!", socket.RemoteEndPoint);
                        socket.Shutdown(SocketShutdown.Both);
                        socket.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        private void RecieveDataTinyOS2()
        {
            try
            {
                while (!stopped)
                {

                    int nBytesRec = socket.Receive(incomingBuffer, 0, 1, SocketFlags.None);
                    if (nBytesRec > 0)
                    {
                        int len = incomingBuffer[0];
                        nBytesRec = socket.Receive(incomingBuffer, 0, len, SocketFlags.None);
                        if (nBytesRec != len)
                            throw new Exception("Not full message received!");
                        byte[] rawMessage = new byte[len-1];
                        Array.Copy(incomingBuffer, 1, rawMessage, 0, len-1);
                        TinyOS2Message rmsg = new TinyOS2Message(rawMessage);
                        if (TOS2MessageArrived != null)
                            TOS2MessageArrived(rmsg);
                    }
                    else
                    {
                        Console.Error.WriteLine("{0} broke the connection!", socket.RemoteEndPoint);
                        socket.Shutdown(SocketShutdown.Both);
                        socket.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        public void Connect()
        {
            try
            {
                if (connected)
                    Disconnect();

                string[] cstrarr = connectionString.Split(new char[] { '@', ':' });
                if (cstrarr[0] != "sf")
                    throw new ArgumentException("Only serialforwarder is supported!");
                if (cstrarr.Length < 2)
                    throw new ArgumentException("Host must be specified!");

                string host = cstrarr[1];

                int port = 9001;
                try
                {
                    if (cstrarr.Length > 2)
                        port = int.Parse(cstrarr[2]);
                }
                catch (FormatException)
                {
                    throw new ArgumentException("Wrong port number format!");
                }
                try
                {
                    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                    socket.Connect(cstrarr[1], port);
                    byte[] buffer = new byte[4];
                    int nb = socket.Receive(buffer, 2, SocketFlags.None);
                    SendString("T!");
                    nb = socket.Receive(buffer, 4, SocketFlags.None);
                    socket.Send(new byte[] { 0, 0, 0, 1 });

                    listenerThread = new Thread(new ThreadStart(RecieveData));
                    listenerThread.Start();

                    connected = true;
                }
                catch (SocketException)
                {
                    connected = false;
                }
            }
            catch
            {

            }
        }

        public void ConnectToTinyOS2()
        {
            try
            {
                if (connected)
                    Disconnect();

                string[] cstrarr = connectionString.Split(new char[] { '@', ':' });
                if (cstrarr[0] != "sf")
                    throw new ArgumentException("Only serialforwarder is supported!");
                if (cstrarr.Length < 2)
                    throw new ArgumentException("Host must be specified!");

                string host = cstrarr[1];

                int port = 9001;
                try
                {
                    if (cstrarr.Length > 2)
                        port = int.Parse(cstrarr[2]);
                }
                catch (FormatException)
                {
                    throw new ArgumentException("Wrong port number format!");
                }
                try
                {
                    socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                    socket.Connect(cstrarr[1], port);
                    SendString("U ");
                    byte[] buffer = new byte[2];
                    int nb = socket.Receive(buffer, 2, SocketFlags.None); // Receive "U "

                    listenerThread = new Thread(new ThreadStart(RecieveDataTinyOS2));
                    listenerThread.Start();

                    connected = true;
                }
                catch (SocketException)
                {
                    connected = false;
                }
            }
            catch
            {

            }
        }

        public void Disconnect()
        {
            if (connected)
            {

                socket.Close();
                connected = false;
                //if (listenerThread.IsAlive)

            }
        }
    }
}
